#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN DATA ABSTRACTION LAYER version 1

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.ComponentModel;
using Thn.Application;
using Thn.Exceptions;
#endregion

namespace Thn.Data
{
    /// <summary>
    /// Converter class for Condition objects
    /// </summary>
    public class ConditionConverter : TypeConverter
    {
        #region Can Convert From
        /// <summary>
        /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context. 
        /// </summary>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            bool result = false;

            if (sourceType.Equals(typeof(string))) result = true;
            else result = base.CanConvertFrom(context, sourceType);

            return result;
        }
        #endregion

        #region Can Convert To
        /// <summary>
        /// Returns whether this converter can convert the object to the specified type, using the specified context
        /// </summary>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            bool result = false;
            
            if (destinationType.Equals(typeof(string))) result = true;
            else result = base.CanConvertTo(context, destinationType);

            return result;
        }
        #endregion

        #region Convert To
        /// <summary>
        /// Converts the given value object to the specified type, using the specified context and culture information. 
        /// </summary>
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            object result = null;

            if (destinationType.Equals(typeof(string))) result = ConditionToString((Condition)value);
            else result = base.ConvertTo(context, culture, value, destinationType);

            return result;
        }
        #endregion

        #region Convert From
        /// <summary>
        /// Converts the given object to the type of this converter, using the specified context and culture information. 
        /// </summary>
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            object result = null;

            if (value is string) result = ParseCondition((string)value, true);
            else result = base.ConvertFrom(context, culture, value);

            return result;
        }
        #endregion        
        
        #region Convert condition to string
        #region Condition To String
        string ConditionToString(Condition value)
        {
            string result = "";

            if (value is SingleCondition) result = SingleToString((SingleCondition)value);
            else if (value is NestedCondition) result = NestedToString((NestedCondition)value);
            else
                UnsupportedException.Publish(value.GetType());            

            return result;
        }
        #endregion

        #region Single To String
        /// <summary>
        /// Converts a single condition to string
        /// </summary>
        string SingleToString(SingleCondition value)
        {
            string result = "";

            string field = value.Field;
            IComparable targetValue = value.Value;

            switch (value.Operation)
            {
                case ComparisionType.Equal:
                    result = "(" + field + " = " + targetValue.GetType().FullName + " \"" + EnvelopeValue(targetValue) + "\")";
                    break;
                case ComparisionType.Greater:
                    result = "(" + field + " > " + targetValue.GetType().FullName + " \"" + EnvelopeValue(targetValue) + "\")";
                    break;
                case ComparisionType.GreaterOrEqual:
                    result = "(" + field + " >= " + targetValue.GetType().FullName + " \"" + EnvelopeValue(targetValue) + "\")";
                    break;
                case ComparisionType.Less:
                    result = "(" + field + " < " + targetValue.GetType().FullName + " \"" + EnvelopeValue(targetValue) + "\")";
                    break;
                case ComparisionType.LessOrEqual:
                    result = "(" + field + " <= " + targetValue.GetType().FullName + " \"" + EnvelopeValue(targetValue) + "\")";
                    break;
                case ComparisionType.NotEqual:
                    result = "(" + field + " <> " + targetValue.GetType().FullName + " \"" + EnvelopeValue(targetValue) + "\")";
                    break;                
                case ComparisionType.NotEmpty:
                    result = "(" + field + " NotEmpty)";
                    break;
                case ComparisionType.Unique:
                    result = "(" + field + " Unique)";
                    break;
            }

            return result;
        }
        #endregion

        #region Nested To String
        /// <summary>
        /// Converts a Nested condition to string
        /// </summary>
        string NestedToString(NestedCondition value)
        {
            string result = "";

            if (value.Combination == CombinationType.And)
                result = "(" + ConditionToString(value.Left) + " & " + ConditionToString(value.Right) + ")";
            else if (value.Combination == CombinationType.Or)
                result = "(" + ConditionToString(value.Left) + " | " + ConditionToString(value.Right) + ")";

            return result;
        }
        #endregion
        #endregion

        #region Parse condition from string
        #region Parse Group
        /// <summary>
        /// Parse a simple group with ( from left and ) from right
        /// </summary>
        string ParseGroup(string text, char startChar, char endChar)
        {
            string result = string.Empty;

            int idxLeft = text.IndexOf(startChar);
            if (idxLeft >= 0)
            {
                int idxRight = text.LastIndexOf(endChar);

                if (idxLeft < idxRight + 1)
                {
                    result = text.Substring(idxLeft + 1, idxRight - idxLeft - 1);
                }
            }

            return result;
        }

        /// <summary>
        /// Parse a complex group from left to right
        /// </summary>
        public string ParseGroupFromLeft(string text, char startChar, char endChar)
        {
            string result = string.Empty;

            int idxLeft = -1;
            int idxRight = -1;
            int count = 0;

            for (int i = 0; i < text.Length; i++)
            {
                char c = text[i];
                
                if (c == startChar)
                {
                    //first time found group opening
                    if (idxLeft == -1) idxLeft = i;

                    count++;
                }
                else if (c == endChar)
                {
                    if (count == 1)
                    {
                        idxRight = i;
                        break;
                    }
                    else count--;
                }
            }

            //copy result
            if ((idxRight > idxLeft) && (idxLeft >= 0))
            {
                result = text.Substring(idxLeft + 1, idxRight - idxLeft - 1);
            }

            return result;
        }

        /// <summary>
        /// Parse a complex group from right to left
        /// </summary>
        public string ParseGroupFromRight(string text, char startChar, char endChar)
        {
            string result = string.Empty;

            int idxLeft = -1;
            int idxRight = -1;
            int count = 0;

            for (int i = text.Length - 1; i >= 0; i--)
            {
                char c = text[i];

                if (c == endChar)
                {
                    //first time found group ending
                    if (idxRight == -1) idxRight = i;

                    count++;
                }
                else if (c == startChar)
                {
                    if (count == 1)
                    {
                        idxLeft = i;
                        break;
                    }
                    else count--;
                }
            }

            //copy result
            if ((idxRight > idxLeft) && (idxLeft >= 0))
            {
                result = text.Substring(idxLeft + 1, idxRight - idxLeft - 1);
            }

            return result;
        }
        #endregion

        #region Parse Condition
        /// <summary>
        /// Parse a generic condition from text
        /// </summary>
        Condition ParseCondition(string text, bool firstTime)
        {
            Condition result = null;

            if (firstTime) text = ParseGroup(text, '(', ')');

            if (text != string.Empty)
            {
                //determine whether this is a nested or a single condition
                if (text.IndexOf('(') >= 0)
                {//Nested condition

                    Condition leftCondition = null;
                    Condition rightCondition = null;

                    //parse left group
                    string leftGroup = ParseGroupFromLeft(text, '(', ')');
                    if (leftGroup == string.Empty) Thn.Exceptions.IncorrectFormatException.Publish();
                    leftCondition = ParseCondition(leftGroup, false);

                    //parse right group
                    string rightGroup = ParseGroupFromRight(text, '(', ')');
                    if (rightGroup == string.Empty) Thn.Exceptions.IncorrectFormatException.Publish();
                    rightCondition = ParseCondition(rightGroup, false);

                    //parse operator
                    int idx = leftGroup.Length + 2;
                    int length = text.Length - rightGroup.Length - leftGroup.Length - 4;
                    string operation = text.Substring(idx, length).Trim();

                    //reconstruct condition
                    if ((leftCondition != null) && (rightCondition != null) && (operation != string.Empty))
                    {
                        if (operation == "&") result = new NestedCondition(leftCondition, rightCondition, CombinationType.And);
                        else if (operation == "|") result = new NestedCondition(leftCondition, rightCondition, CombinationType.Or);
                    }
                    else Thn.Exceptions.IncorrectFormatException.Publish();

                }//Nested condition
                else
                {//Single condition
                    result = ParseSingle(text);
                }//Single condition
            }

            return result;
        }
        #endregion

        #region Parse Single
        SingleCondition ParseSingle(string text)
        {
            SingleCondition result = null;
            string field = "";
            string operation = "";
            string targetType = "";
            string targetValue = "";
            int left = 0;            

            //parse field
            int idx = text.IndexOf(' ', left);
            if (idx > left)
            {
                field = text.Substring(left, idx - left);
                left = idx + 1;
            }
            else IncorrectFormatException.Publish("text", "(field Unique/NotEmpty ) / (field operation type \"text\")");

            //parse operation
            idx = text.IndexOf(' ', left);
            if (idx > left)
            {
                operation = text.Substring(left, idx - left).ToUpper();
                left = idx + 1;
            }
            else
            {
                operation = text.Substring(left).Trim().ToUpper();
            }

            if (operation == "UNIQUE")
            {
                //reconstruct condition
                result = Condition.Unique(field);
            }
            else if (operation == "NOTEMPTY")
            {
                //reconstruct condition
                result = Condition.NotEmpty(field);
            }
            else
            {
                //parse text type
                idx = text.IndexOf(' ', left);
                if (idx > left)
                {
                    targetType = text.Substring(left, idx - left);
                    left = idx + 1;
                }
                else IncorrectFormatException.Publish("text", "(field Unique/NotEmpty ) / (field operation type \"text\")");

                //parse target text                
                targetValue = ParseGroup(text, '"', '"');
                if (targetValue==string.Empty)
                {
                    IncorrectFormatException.Publish("text", "(field Unique/NotEmpty ) / (field operation type \"text\")");
                }

                //reconstruct condition
                object tmp = DevelopeValue(targetValue, targetType);

                if (tmp is IComparable)
                {
                    if (operation == "=") result = new SingleCondition(field, (IComparable)tmp, ComparisionType.Equal);
                    else if (operation == ">") result = new SingleCondition(field, (IComparable)tmp, ComparisionType.Greater);
                    else if (operation == ">=") result = new SingleCondition(field, (IComparable)tmp, ComparisionType.GreaterOrEqual);
                    else if (operation == "<") result = new SingleCondition(field, (IComparable)tmp, ComparisionType.Less);
                    else if (operation == "<=") result = new SingleCondition(field, (IComparable)tmp, ComparisionType.LessOrEqual);
                    else if (operation == "<>") result = new SingleCondition(field, (IComparable)tmp, ComparisionType.NotEqual);
                }
                else IncompatibleTypeException.Publish("Value", tmp, typeof(IComparable));
            }

            return result;
        }
        #endregion
        #endregion

        #region Envelope / Develop value
        #region Envelope Value
        string EnvelopeValue(object value)
        {
            string result = string.Empty;

            if (value is DateTime) result = ((DateTime)value).ToString();
            else
            {
                TypeConverter converter = TypeDescriptor.GetConverter(value);
                result = converter.ConvertToString(value);
            }

            //normalize special characters
            result = result.Replace("(", "&left;");
            result = result.Replace(")", "&right;");
            result = result.Replace("\"", "&quote;");

            return result;
        }
        #endregion

        #region Develop Value
        object DevelopeValue(string text, string dataType)
        {
            object result = null;

            Type t = TypeFinder.GetType(dataType);
            if (t == null) TypeNotFoundException.Publish(dataType);
            else
            {
                //denormalize special characters
                text = text.Replace("&left;", "(");
                text = text.Replace("&right;", ")");
                text = text.Replace("&quote;", "\"");

                TypeConverter converter = TypeDescriptor.GetConverter(t);
                result = converter.ConvertFromString(text);
            }

            return result;
        }
        #endregion
        #endregion
    }
}
